using System;
using System.Collections.Generic;
using System.Text;
using System.Data.Common;
using NBusiness.Utilities;
using System.Configuration;
using System.Collections.ObjectModel;
using System.Threading;

namespace NBusiness.Data
{
    // TODO: Create authorization rules in the templates for siblings!

    [Serializable]
    public abstract class SiblingBaseCollection<T> :
        ICollection<T>,
        IEntityCollection
        where T : EntityBase, new()
    {
        #region Constructors
        protected SiblingBaseCollection(EntityBase leftSibling)
            : this()
        {
            _leftSibling = leftSibling;
            string connectionStringName = (string)Settings.Instance.GetSetting(SettingsType.ConnectionStringName, this.GetType());
            string connstr = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
            using (DbConnection cn = ((IDatabaseProvider)Settings.Instance.GetSetting(SettingsType.Provider, this.GetType())).GetConnection(connstr))
            {
                cn.Open();
                Fetch(cn, leftSibling);
            }
        }
        protected SiblingBaseCollection(DbConnection cn, EntityBase leftSibling)
            : this()
        {
            _leftSibling = leftSibling;
            Fetch(cn, leftSibling);
        }

        protected SiblingBaseCollection()
        {
            try
            {
                T t = new T();
                Initialize();
            }
            catch (Exception innerEx)
            {
                try
                {
                    OnError(innerEx);
                }
                catch (Exception outerEx)
                {
                    throw new EntityErrorException("Failed to handle Error", innerEx, outerEx);
                }

                throw innerEx;
            }
        }
        #endregion

        #region Initialize
        /// <summary>
        /// Implement in partial class to allow user code to attach events and implement custom code.
        /// </summary>
        public virtual void Initialize()
        {
        }
        #endregion

        #region Left and Right siblings
        private EntityBase _leftSibling = null;
        protected EntityBase LeftSibling
        {
            get { return _leftSibling; }
        }
        #endregion

        #region Authorization Rules
        public bool IsValid 
        {
            get
            {
                bool valid = true;
                foreach (T t in Items)
                {
                    if (!t.IsValid)
                    {
                        valid = false;
                        break;
                    }
                }
                return valid;
            }
        }
        /// <summary>
        /// Check to see if this user can Fetch this object.
        /// </summary>
        /// <returns></returns>
        public virtual bool CanFetch()
        {
            return EntityBase.AuthorizationRules.CanFetch(typeof(T), Thread.CurrentPrincipal);
        }
        /// <summary>
        /// Check to see if this user can Insert this object.
        /// </summary>
        /// <returns></returns>
        public virtual bool CanInsert()
        {
            return EntityBase.AuthorizationRules.CanInsert(typeof(T), Thread.CurrentPrincipal);
        }
        /// <summary>
        /// Check to see if this user can Update this object.
        /// </summary>
        /// <returns></returns>
        public virtual bool CanUpdate()
        {
            return EntityBase.AuthorizationRules.CanUpdate(typeof(T), Thread.CurrentPrincipal);
        }
        /// <summary>
        /// Check to see if this user can Delete this object.
        /// </summary>
        /// <returns></returns>
        public virtual bool CanDelete()
        {
            return EntityBase.AuthorizationRules.CanDelete(typeof(T), Thread.CurrentPrincipal);
        }
        #endregion

        #region Fetch
        protected virtual void Fetch(DbConnection cn, EntityBase leftSibling)
        {
            FetchSiblings(cn, leftSibling);
        }
        protected abstract void FetchSiblings(DbConnection cn, EntityBase leftSibling);
        /// <summary>
        /// Override this method to implement loading Entities after a fetch.
        /// </summary>
        /// <param name="dr"></param>
        public abstract void Load(object sender, LoadEventArgs e);
        #endregion

        #region Siblings added or removed
        /// <summary>
        /// Called during persistence for each Entity that has been removed from the list since the last
        /// time the list was persisted/fetched.
        /// </summary>
        /// <param name="Transaction"></param>
        /// <param name="t"></param>
        protected abstract void RemoveSibling(DbTransaction transaction, T sibling);
        /// <summary>
        /// Called during persistence for each Entity that has been added to the list since the last time
        /// the list was persisted/fetched.
        /// </summary>
        /// <param name="Transaction"></param>
        /// <param name="t"></param>
        protected abstract void AddSibling(DbTransaction transaction, T sibling);
        #endregion

		#region Member properties and fields
        private Collection<T> _deletedList = new Collection<T>();
        private Collection<T> _addedList = new Collection<T>();
        private Collection<T> _items = new Collection<T>();
        protected Collection<T> Items
        {
            get { return _items; }
        }
        #endregion

        #region ICollection interfaces
        #region ICollection<T> Members

        /// <summary>
        /// Override this to add behavior to how the item is added. By default _Items.Add(item) is called.
        /// You may wish to change some fields as an item is added, for example.
        /// </summary>
        /// <param name="item"></param>
        public virtual void Add(T item)
        {
            item.Deleting += new EventHandler<DeleteEventArgs>(SiblingDeleting);
            item.Persisted += new EventHandler<PersistEventArgs>(SiblingPersisted);
            _addedList.Add(item);
            _items.Add(item);
            OnAdded(item);
        }

        void SiblingPersisted(object sender, PersistEventArgs e)
        {
            LeftSibling.Persist(e.Transaction, PersistType.Downward);
        }

        void SiblingDeleting(object sender, DeleteEventArgs e)
        {
            Remove((T)sender);
            Persist(e.Transaction, PersistType.Downward);
        }

        public event EventHandler<EventArgs<T>> Added;
        protected virtual void OnAdded(T item)
        {
            if (Added != null) Added(this, new EventArgs<T>(item));
        }

        public virtual void Clear()
        {
            foreach (T t in _items)
            {
                _deletedList.Add(t);
                OnRemoved(t);
            }
            _items.Clear();
        }

        public bool Contains(T item)
        {
            return _items.Contains(item);
        }

        public virtual void CopyTo(T[] array, int arrayIndex)
        {
            _items.CopyTo(array, arrayIndex);
        }

        public virtual int Count
        {
            get { return _items.Count; }
        }

        public bool IsReadOnly
        {
            get{return false;}
        }
        public T this[int index]
        {
            get { return _items[index]; }
            set
            {
                if (_items[index] != null)
                {
                    T item = _items[index];
                    Remove(item);
                }
                _items[index] = value;
            }
        }
        public bool Remove(T item)
        {
            bool removed = false;
            if (_items.Contains(item))
            {
                _items.Remove(item);
                _addedList.Remove(item);
                _deletedList.Add(item);
                OnRemoved(item);

                removed = true;
            }
            return removed;
        }
        /// <summary>
        /// This method is called when a
        /// </summary>
        public event EventHandler<EventArgs<T>> Removed;
        protected virtual void OnRemoved(T item)
        {
            if (Removed != null) Removed(this, new EventArgs<T>(item));
        }
        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            return ((IEnumerable<T>)_items).GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _items.GetEnumerator();
        }

        #endregion
        #endregion

        #region Save
        public void Save()
        {
            string connectionStringName = (string)Settings.Instance.GetSetting(SettingsType.ConnectionStringName, this.GetType());
            string connstr = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
            using (DbConnection cn = ((IDatabaseProvider)Settings.Instance.GetSetting(SettingsType.Provider, this.GetType())).GetConnection(connstr))
            {
                cn.Open();
                using (DbTransaction trans = cn.BeginTransaction())
                {
                    try
                    {
                        Persist(trans, PersistType.Downward);
                    }
                    catch
                    {
                        trans.Rollback();
                        throw;
                    }
                    trans.Commit();
                }
            }
        }
        private PersistenceGuard _persistenceGuard = null;

        public void Persist(DbTransaction transaction, PersistType persistType)
        {
            if (_persistenceGuard == null || _persistenceGuard.Persisting == false)
            {
                using (_persistenceGuard = new PersistenceGuard())
                {
                    OnPersisting(transaction);

                    //Persist deletes before persisting individual entities.
                    OnDeleting(transaction);
                    foreach (T t in _deletedList)
                    {
                        RemoveSibling(transaction, t);
                        t.Deleted -= new EventHandler<DeleteEventArgs>(SiblingDeleting);
                        t.Persisted -= new EventHandler<PersistEventArgs>(SiblingPersisted);
                    }
                    _deletedList.Clear();
                    OnDeleted(transaction);

                    //Save changes to individual entities that may have occured.
                    foreach (T t in _items)
                        t.Persist(transaction, persistType);

                    //Insert new siblings added so far.
                    foreach (T t in _addedList)
                    {
                        AddSibling(transaction, t);
                    }
                    _addedList.Clear();

                    OnPersisted(transaction);
                }
            }
        }
        #endregion

        #region IBusinessObject Members


        public void Delete()
        {
            Clear();
        }

        public bool IsNew
        {
            get
            {
                bool isnew = false;
                foreach (T t in _items)
                {
                    if (t.IsNew)
                    {
                        isnew = true;
                        break;
                    }
                }
                return isnew;
            }
        }

        public bool IsDeleted
        {
            get { return _items.Count == 0 && _deletedList.Count > 0; }
        }

        #endregion

        #region Error
        public event EventHandler<EventArgs<Exception>> Error;
        protected virtual void OnError(Exception ex)
        {
            if (Error != null) Error(this, new EventArgs<Exception>(ex));
        }
        #endregion

        #region IEntityCollection Members

        EntityBase IEntityCollection.this[int index]
        {
            get
            {
                return this[index];
            }
            set
            {
                if (!typeof(T).IsAssignableFrom(value.GetType()))
                    throw new ArgumentException("Invalid Type, must be " + typeof(T).ToString(), "value");

                this[index] = (T)value;
            }
        }

        public int IndexOf(EntityBase entity)
        {
            if (!typeof(T).IsAssignableFrom(entity.GetType()))
                throw new ArgumentException("Invalid Type, must be " + typeof(T).ToString(), "value");

            return this._items.IndexOf((T)entity);
        }

        public void Remove(EntityBase entity)
        {
            if (!typeof(T).IsAssignableFrom(entity.GetType()))
                throw new ArgumentException("Invalid Type, must be " + typeof(T).ToString(), "value");

            Remove((T)entity);
        }

        public void Add(EntityBase entity)
        {
            if (!typeof(T).IsAssignableFrom(entity.GetType()))
                throw new ArgumentException("Invalid Type, must be " + typeof(T).ToString(), "value");

            Add((T)entity);
        }

        #endregion


        #region IPersistable Members


        public event EventHandler<PersistEventArgs> Persisting;
        protected virtual void OnPersisting(DbTransaction transaction)
        {
            if (Persisting != null)
                Persisting(this, new PersistEventArgs(transaction));
        }

        public event EventHandler<PersistEventArgs> Persisted;
        protected virtual void OnPersisted(DbTransaction transaction)
        {
            if (Persisted != null)
                Persisted(this, new PersistEventArgs(transaction));
        }
        #endregion

        #region IDeletable Members
        public event EventHandler<DeleteEventArgs> Deleting;
        protected virtual void OnDeleting(DbTransaction transaction)
        {
            if (Deleting != null)
                Deleting(this, new DeleteEventArgs(transaction));
        }

        public event EventHandler<DeleteEventArgs> Deleted;
        protected virtual void OnDeleted(DbTransaction transaction)
        {
            if (Deleted != null)
                Deleted(this, new DeleteEventArgs(transaction));
        }
        #endregion
    }
}
